package elec315.chatroom.net;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import elec315.chatroom.core.dispatcher.MessageDispatcher;
import elec315.chatroom.msg.BaseMsg;
import elec315.chatroom.msg.chat.ChatBroadcastReqMsg;
import elec315.chatroom.server.Observer;
import elec315.chatroom.server.Publisher;

public class ServerSocketHandler extends Thread implements SocketHandler, Publisher, Observer {

	private static final Log logger = LogFactory.getLog(ServerSocketHandler.class);

	private static final List<Observer> observers = new ArrayList<Observer>();

	private final MessageDispatcher messageDispatcher;

	private Socket socket;
	private ObjectOutputStream toClient;
	private ObjectInputStream fromClient;
	private String userId;

	public ServerSocketHandler(Socket clientSocket, MessageDispatcher messageDispatcher) throws IOException {
		this.socket = clientSocket;
		this.messageDispatcher = messageDispatcher;
		fromClient = new ObjectInputStream(socket.getInputStream());
		toClient = new ObjectOutputStream(socket.getOutputStream());
		registerObserver(this);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.lang.Thread#run()
	 */
	@Override
	public void run() {
		try {
			while (true) {
				Object obj = null;
				while ((obj = fromClient.readObject()) != null) {
					if (obj instanceof BaseMsg) {
						BaseMsg baseMsg = (BaseMsg) obj;
						messageDispatcher.onMessage(baseMsg, this);
					}
				}
			}
		} catch (Exception ex) {
			logger.error("Client is disconnected from " + socket.getInetAddress());
		} finally {
			// force logout
			removeObserver(this);
			if (StringUtils.isNotBlank(userId)) {
				String serverId = "***Chat Server***";
				String message = userId + " is disconnected.";
				ChatBroadcastReqMsg reqMsg = new ChatBroadcastReqMsg(serverId, new Date(), serverId, message);
				messageDispatcher.onMessage(reqMsg, this);
			}
			try {
				fromClient.close();
				toClient.close();
				socket.close();
			} catch (IOException ex) {
				logger.error(ex, ex);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see elec315.chatroom.net.SocketHandler#writeMessage(elec315.chatroom.msg.BaseMsg)
	 */
	@Override
	public void writeMessage(BaseMsg baseMsg) {
		synchronized (toClient) {
			try {

				toClient.writeObject(baseMsg);
				toClient.flush();
			} catch (IOException ex) {
				logger.error(ex, ex);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see elec315.chatroom.server.Publisher#broadcastMessage(elec315.chatroom.msg.BaseMsg)
	 */
	@Override
	public void broadcastMessage(BaseMsg baseMsg) {
		for (Observer observer : observers) {
			observer.writeMessage(baseMsg);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see elec315.chatroom.server.Publisher#registerObserver(elec315.chatroom.server.Observer)
	 */
	public void registerObserver(Observer observer) {
		if (!observers.contains(observer)) {
			observers.add(observer);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see elec315.chatroom.server.Publisher#removeObserver(elec315.chatroom.server.Observer)
	 */
	public void removeObserver(Observer observer) {
		if (observers.contains(observer)) {
			observers.remove(observer);
		}
	}

	public String getUserId() {
		return userId;
	}

	public void setUserId(String userId) {
		this.userId = userId;
	}

}